package de.upb.aquarium.api.light;

import java.util.ArrayList;

import javax.media.opengl.GL;
import javax.naming.InsufficientResourcesException;

import de.upb.aquarium.World;
import de.upb.aquarium.api.utility.VectorFloat;

/**
 * An abstract light source. Mainly concerned with resource management.
 * @author Jost
 */
public abstract class Light {
	static ArrayList<Integer> usedLights;
	static ArrayList<Integer> freeLights;
	
	static
	{
		freeLights = new ArrayList<Integer>(8);
		usedLights = new ArrayList<Integer>(8);
		
		// It is guaranteed by the opengl spec that GL_LIGHT0 + i ==
		// GL_LIGHTi, so this adds all light constants.
		for (int i = 0; i < GL.GL_MAX_LIGHTS; i++)
		{
			freeLights.add(GL.GL_LIGHT0 + i);
		}
	}
	
	private boolean isActive;
	private int lightIdentifier;
	
	private VectorFloat colorAmbient;
	private VectorFloat colorDiffuse;
	private VectorFloat colorSpecular;
	
	public Light(
			VectorFloat colorAmbient,
			VectorFloat colorDiffuse,
			VectorFloat colorSpecular) {
		
		if (colorAmbient.getSize() < 4
		 || colorDiffuse.getSize() < 4
		 ||	colorSpecular.getSize() < 4)
		{
			throw new IllegalArgumentException("Each color of a light must " +
					"have at least 4 components (RGBA).");
		}
		
		this.colorAmbient = colorAmbient;
		this.colorDiffuse = colorDiffuse;
		this.colorSpecular = colorSpecular;
		
		isActive = false;
		lightIdentifier = 0; // doesn't mean anything, but member is initialized.
	}
	
	/**
	 * This method is called when the light is enabled. A subclass should
	 * initialize the light in this method.
	 * @param gl The gl object to use.
	 * @param lightIdentifier The light identifier to use (one of the
	 * 		GL.GL_LIGHT0, ..., GL.GL_LIGHT7 identifiers).
	 */
	public abstract void enable(GL gl, int lightIdentifier);
	
	/**
	 * This method is called when the light is disabled. It provides the
	 * subclass with an opportunity to free any resources. It may safely
	 * be left unimplemented.
	 */
	public abstract void disable();
	
	/**
	 * Activates or deactivates this light. This method calls
	 * <code>World.getInstance().getGL()</code>, so make sure the GL object is
	 * currently usable.
	 * @param on Enable or disable?
	 * @throws InsufficientResourcesException If the light is enabled, but
	 * 		there is already the maximal number of lights enabled.
	 */
	public void setOn(boolean on) throws InsufficientResourcesException
	{	
		if (on)
		{
			if (freeLights.isEmpty())
			{
				throw new InsufficientResourcesException("There are only " +
						GL.GL_MAX_LIGHTS + "lights allowed at one time.");
			}
			
			if (!isActive)
			{
				isActive = true;
				lightIdentifier = freeLights.get(0); // index is valid.
			
				freeLights.remove(0);
				usedLights.add(lightIdentifier);
			}

			// Get the global GL object.
			GL gl = World.getInstance().getGL();

			// Set the colors:
			gl.glLightfv(lightIdentifier, GL.GL_AMBIENT, colorAmbient.data, 0);
			gl.glLightfv(lightIdentifier, GL.GL_DIFFUSE, colorDiffuse.data, 0);
			gl.glLightfv(lightIdentifier, GL.GL_SPECULAR, colorSpecular.data, 0);
			
			// Let the subclass configure itself (it overrides color settings
			// from here):
			enable(gl, lightIdentifier);
			
			// Enable the light
			gl.glEnable(lightIdentifier);
		}
		else
		{
			if (isActive)
			{
				// This light should be marked as used... but one never knows.
				assert(usedLights.contains(lightIdentifier));
				
				isActive = false;
				usedLights.remove(lightIdentifier);
				freeLights.add(lightIdentifier);
				
				lightIdentifier = 0; // Something hopefully invalid.
			}
			
			// Give the subclass the chance to free resources etc.:
			disable();
			
			// Disable the light
			World.getInstance().getGL().glDisable(lightIdentifier);
		}
	}
	
	/**
	 * Returns if this light is on.
	 * @return Is this light on?
	 */
	public boolean isOn()
	{
		return isActive;
	}
	
	/**
	 * Gets the lights identifier for use with OpenGL. The return value is only
	 * valid when the light has been enabled by a call to <code>setOn(true)
	 * </code> before. 
	 * @return one of GL.GL_LIGHT0, ..., GL.GL_LIGHT7
	 */
	protected int getLightIdentifier()
	{
		return lightIdentifier;
	}
	
	public VectorFloat getColorAmbient() {
		return colorAmbient;
	}

	public void setColorAmbient(VectorFloat colorAmbient) {
		this.colorAmbient = colorAmbient;
	}

	public VectorFloat getColorDiffuse() {
		return colorDiffuse;
	}

	public void setColorDiffuse(VectorFloat colorDiffuse) {
		this.colorDiffuse = colorDiffuse;
	}

	public VectorFloat getColorSpecular() {
		return colorSpecular;
	}

	public void setColorSpecular(VectorFloat colorSpecular) {
		this.colorSpecular = colorSpecular;
	}
}
